﻿/*
 * Released under Microsoft Public License (Ms-PL) 
 * Sponsored by Development Platform Evangelism unit of Microsoft Israel
 * 
 * Copyright © 2008 by Tamir Khason
 * http://blogs.microsoft.co.il/blogs/tamir/
 * http://sharpsoft.net/
 * 
 * More information including licensing and term of use
 * can be found on http://www.codeplex.com/SilverlightRTL/
 * 
 */

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows.Markup;
using System.Collections.Generic;
using System.Text;
using System.Windows.Browser;

namespace System.Windows.BidiControls
{
    /// <summary>
    /// Represents a control that can be used for text edit or display
    /// </summary>
    [ContentProperty("Text")]
    [TemplatePart(Name = TextBox.TextElementName, Type = typeof(System.Windows.Controls.TextBox))]
    public class TextBox : System.Windows.BidiControls.Control
    {
        #region props

        #region AcceptsReturn

        /// <summary> 
        /// Gets or sets the AcceptsReturn possible Value of the bool object.
        /// </summary> 
        public bool AcceptsReturn
        {
            get { return (bool)GetValue(AcceptsReturnProperty); }
            set { SetValue(AcceptsReturnProperty, value); }
        }

        /// <summary> 
        /// Identifies the AcceptsReturn dependency property.
        /// </summary> 
        public static readonly DependencyProperty AcceptsReturnProperty =
                    DependencyProperty.Register(
                          "AcceptsReturn",
                          typeof(bool),
                          typeof(TextBox),
                          null);

        #endregion AcceptsReturn

        #region IsReadOnly

        /// <summary> 
        /// Gets or sets the IsReadOnly possible Value of the bool object.
        /// </summary> 
        public bool IsReadOnly
        {
            get { return (bool)GetValue(IsReadOnlyProperty); }
            set { SetValue(IsReadOnlyProperty, value); }
        }

        /// <summary> 
        /// Identifies the IsReadOnly dependency property.
        /// </summary> 
        public static readonly DependencyProperty IsReadOnlyProperty =
                    DependencyProperty.Register(
                          "IsReadOnly",
                          typeof(bool),
                          typeof(TextBox),
                          null);
        #endregion IsReadOnly

        #region MaxLength

        /// <summary> 
        /// Gets or sets the MaxLength possible Value of the int object.
        /// </summary> 
        public int MaxLength
        {
            get { return (int)GetValue(MaxLengthProperty); }
            set { SetValue(MaxLengthProperty, value); }
        }

        /// <summary> 
        /// Identifies the MaxLength dependency property.
        /// </summary> 
        public static readonly DependencyProperty MaxLengthProperty =
                    DependencyProperty.Register(
                          "MaxLength",
                          typeof(int),
                          typeof(TextBox),
                          null);

        #endregion MaxLength

        #region TextAlignment

        /// <summary> 
        /// Gets or sets the TextAlignment possible Value of the TextAlignment object.
        /// </summary> 
        [TypeConverter(typeof(EnumValueConverter<TextAlignment>))]
        [DefaultValue(TextAlignment.Right)]
        public TextAlignment TextAlignment
        {
            get { return (TextAlignment)GetValue(TextAlignmentProperty); }
            set { SetValue(TextAlignmentProperty, value); }
        }

        /// <summary> 
        /// Identifies the TextAlignment dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextAlignmentProperty =
                    DependencyProperty.Register(
                          "TextAlignment",
                          typeof(TextAlignment),
                          typeof(TextBox),
                          new PropertyMetadata(OnTextAlignmentPropertyChanged));
        //PATCH: workaround for DefaultValueAttribute bug
        bool tapWasSet = false;
        private static void OnTextAlignmentPropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
        {
            TextBox _tb = s as TextBox;
            if (_tb != null)
            {
                //PATCH: workaround for DefaultValueAttribute bug
                _tb.tapWasSet = true;
            }
        }
        #endregion TextAlignment

        #region TextWrapping

        /// <summary> 
        /// Gets or sets the TextWrapping possible Value of the TextWrapping object.
        /// </summary> 
        public TextWrapping TextWrapping
        {
            get { return (TextWrapping)GetValue(TextWrappingProperty); }
            set { SetValue(TextWrappingProperty, value); }
        }

        /// <summary> 
        /// Identifies the TextWrapping dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextWrappingProperty =
                    DependencyProperty.Register(
                          "TextWrapping",
                          typeof(TextWrapping),
                          typeof(TextBox),
                          null);

        #endregion TextWrapping

        #region AllowAlignmentOnFlowChange

        /// <summary> 
        /// Allows TextAlignment change when TextFlow property changed
        /// Gets or sets the AllowAlignmentOnFlowChange possible Value of the bool object.
        /// </summary> 
        [DefaultValue(true)]
        public bool AllowAlignmentOnFlowChange
        {
            get { return (bool)GetValue(AllowAlignmentOnFlowChangeProperty); }
            set { SetValue(AllowAlignmentOnFlowChangeProperty, value); }
        }

        /// <summary> 
        /// Identifies the AllowAlignmentOnFlowChange dependency property.
        /// </summary> 
        public static readonly DependencyProperty AllowAlignmentOnFlowChangeProperty =
                    DependencyProperty.Register(
                          "AllowAlignmentOnFlowChange",
                          typeof(bool),
                          typeof(TextBox),
                          null);

        #endregion AllowAlignmentOnFlowChange

        #region FontSource
        /// <summary>
        /// Get or sets the font source that is applied to the text box for rendering content
        /// </summary>
        public FontSource FontSource
        {
            get
            {
                if (TextElement != null)
                    return TextElement.FontSource;
                return default(FontSource);
            }
            set
            {
                if (TextElement != null)
                    TextElement.FontSource = value;
            }
        }
        #endregion FontSource

        #region HorizontalScrollBarVisibility
        /// <summary>
        /// Get or sets the visibility of horizontal scroll bar
        /// </summary>
        public ScrollBarVisibility HorizontalScrollBarVisibility
        {
            get
            {
                if (TextElement != null)
                    return TextElement.HorizontalScrollBarVisibility;
                return default(ScrollBarVisibility);
            }
            set
            {
                if (TextElement != null)
                    TextElement.HorizontalScrollBarVisibility = value;
            }
        }
        #endregion HorizontalScrollBarVisibility

        #region VerticalScrollBarVisibility
        /// <summary>
        /// Get or sets the visibility of vertical scroll bar
        /// </summary>
        public ScrollBarVisibility VerticalScrollBarVisibility
        {
            get
            {
                if (TextElement != null)
                    return TextElement.VerticalScrollBarVisibility;
                return default(ScrollBarVisibility);
            }
            set
            {
                if (TextElement != null)
                    TextElement.VerticalScrollBarVisibility = value;
            }
        }
        #endregion VerticalScrollBarVisibility

        #region SelectionBackground

        /// <summary> 
        /// Gets or sets the SelectionBackground possible Value of the Brush object.
        /// </summary> 
        public Brush SelectionBackground
        {
            get { return (Brush)GetValue(SelectionBackgroundProperty); }
            set { SetValue(SelectionBackgroundProperty, value); }
        }

        /// <summary> 
        /// Identifies the SelectionBackground dependency property.
        /// </summary> 
        public static readonly DependencyProperty SelectionBackgroundProperty =
                    DependencyProperty.Register(
                          "SelectionBackground",
                          typeof(Brush),
                          typeof(TextBox),
                          new PropertyMetadata(OnSelectionBackgroundPropertyChanged));

        /// <summary>
        /// SelectionBackgroundProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBox that changed its SelectionBackground.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnSelectionBackgroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox _TextBox = d as TextBox;
            if (_TextBox != null && _TextBox.TextElement != null)
            {
                _TextBox.TextElement.SelectionBackground = (Brush)e.NewValue;
            }
        }



        #endregion SelectionBackground

        #region SelectionForeground

        /// <summary> 
        /// Gets or sets the SelectionForeground possible Value of the Brush object.
        /// </summary> 
        public Brush SelectionForeground
        {
            get { return (Brush)GetValue(SelectionForegroundProperty); }
            set { SetValue(SelectionForegroundProperty, value); }
        }

        /// <summary> 
        /// Identifies the SelectionForeground dependency property.
        /// </summary> 
        public static readonly DependencyProperty SelectionForegroundProperty =
                    DependencyProperty.Register(
                          "SelectionForeground",
                          typeof(Brush),
                          typeof(TextBox),
                          new PropertyMetadata(OnSelectionForegroundPropertyChanged));

        /// <summary>
        /// SelectionForegroundProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBox that changed its SelectionForeground.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnSelectionForegroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox _TextBox = d as TextBox;
            if (_TextBox != null && _TextBox.TextElement != null)
            {
                _TextBox.TextElement.SelectionForeground = (Brush)e.NewValue;
            }
        }

        #endregion SelectionForeground

        #region SelectedText

        /// <summary> 
        /// Gets or sets the SelectedText possible Value of the string object.
        /// </summary> 
        public string SelectedText
        {
            get { return (string)GetValue(SelectedTextProperty); }
            set { SetValue(SelectedTextProperty, value); }
        }

        /// <summary> 
        /// Identifies the SelectedText dependency property.
        /// </summary> 
        private static readonly DependencyProperty SelectedTextProperty =
                    DependencyProperty.Register(
                          "SelectedText",
                          typeof(string),
                          typeof(TextBox),
                          new PropertyMetadata(OnSelectedTextPropertyChanged));

        /// <summary>
        /// SelectedTextProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBox that changed its SelectedText.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnSelectedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox _TextBox = d as TextBox;
            if (_TextBox != null && _TextBox.TextElement != null)
            {
                _TextBox.TextElement.SelectedText = (string)e.NewValue;
            }
        }
        #endregion SelectedText

        #region SelectionLength

        /// <summary> 
        /// Gets or sets the SelectionLength possible Value of the int object.
        /// </summary> 
        public int SelectionLength
        {
            get { return (int)GetValue(SelectionLengthProperty); }
            set { SetValue(SelectionLengthProperty, value); }
        }

        /// <summary> 
        /// Identifies the SelectionLength dependency property.
        /// </summary> 
        private static readonly DependencyProperty SelectionLengthProperty =
                    DependencyProperty.Register(
                          "SelectionLength",
                          typeof(int),
                          typeof(TextBox),
                          new PropertyMetadata(OnSelectionLengthPropertyChanged));

        /// <summary>
        /// SelectionLengthProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBox that changed its SelectionLength.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnSelectionLengthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox _TextBox = d as TextBox;
            if (_TextBox != null && _TextBox.TextElement != null)
            {
                _TextBox.TextElement.SelectionLength = (int)e.NewValue;
            }
        }
        #endregion SelectionLength

        #region SelectionStart

        /// <summary> 
        /// Gets or sets the SelectionStart possible Value of the int object.
        /// </summary> 
        public int SelectionStart
        {
            get { return (int)GetValue(SelectionStartProperty); }
            set { SetValue(SelectionStartProperty, value); }
        }

        /// <summary> 
        /// Identifies the SelectionStart dependency property.
        /// </summary> 
        private static readonly DependencyProperty SelectionStartProperty =
                    DependencyProperty.Register(
                          "SelectionStart",
                          typeof(int),
                          typeof(TextBox),
                          new PropertyMetadata(OnSelectionStartPropertyChanged));

        /// <summary>
        /// SelectionStartProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBox that changed its SelectionStart.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnSelectionStartPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox _TextBox = d as TextBox;
            if (_TextBox != null && _TextBox.TextElement != null)
            {
                _TextBox.TextElement.SelectionStart = (int)e.NewValue;
            }
        }
        #endregion SelectionStart

        #region Text

        /// <summary> 
        /// Gets or sets the Text possible Value of the string object.
        /// </summary> 
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        /// <summary> 
        /// Identifies the Text dependency property.
        /// </summary> 
        public static readonly DependencyProperty TextProperty =
                    DependencyProperty.Register(
                          "Text",
                          typeof(string),
                          typeof(TextBox),
                          new PropertyMetadata(OnTextPropertyChanged));

        /// <summary>
        /// TextProperty property changed handler. 
        /// </summary>
        /// <param name="d">TextBox that changed its Text.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox _TextBox = d as TextBox;
            if (_TextBox != null && _TextBox.TextElement !=null)
            {
                _TextBox.resetTextData((string)e.NewValue);
            }
        }

        private void resetTextData(string data)
        {
            Dispatcher.BeginInvoke(delegate
                {
                    TextElement.TextChanged -= onInternalTextChanged;
                    if (AllowAlignmentOnFlowChange)
                    {
                        this.TextAlignment = this.TextAlignment == TextAlignment.Left ? TextAlignment.Right : TextAlignment.Left;
                    }

                    if (this.FlowDirection == FlowDirection.RightToLeft)
                    {
                        //add stack strategy (after layout)

                        if (!string.IsNullOrEmpty(data) && this.TextWrapping == TextWrapping.Wrap && this.ActualWidth > 0)
                        {
                            //cannot get glyphs. Use fallback rounded chars
                            char nl = (char)10;
                            double pxC = FontSize * .72;
                            StringBuilder sb = new StringBuilder(data);
                            int idx = 0;
                            int pidx = 0;
                            for (int i = 0; i < sb.Length; i++)
                            {
                                NBidi.BidiCharacterType ct = NBidi.UnicodeCharacterDataResolver.GetBidiCharacterType(sb[i]);
                                if (ct == NBidi.BidiCharacterType.NSM) continue;
                                if (ct == NBidi.BidiCharacterType.WS || ct == NBidi.BidiCharacterType.B || i == sb.Length - 1)
                                {
                                    //can wrap
                                    if (NBidi.UnicodeCharacterDataResolver.GetBidiCharacterType(sb[pidx]) == NBidi.BidiCharacterType.B |
                                        idx * pxC >= this.ActualWidth)
                                    {
                                        sb[pidx] = nl;
                                        idx = i - pidx;
                                    }

                                    string sl = NBidi.NBidi.LogicalToVisual(data.Substring(pidx, i - pidx));
                                    idx += sl.CountVisibleLetter();

                                    pidx = i;
                                }
                                //idx++;
                            }
                            data = sb.ToString();
                        }
                        this.TextElement.Text = NBidi.NBidi.LogicalToVisual(NBidi.BidiChars.RLM + data);

                    }
                    else
                    {
                        this.TextElement.Text = string.IsNullOrEmpty(data) ? string.Empty : data;
                    }
                    TextElement.TextChanged += onInternalTextChanged;
                });
        }
        #endregion Text

        #endregion

        #region fields
        string lastStr = string.Empty;
        int lastPos = 0;
        int newPosOffset = 0;
        bool IsCtrlDown, IsShiftDown, IsAltDown;
        #endregion

        #region ctor
        public TextBox()
        {
            DefaultStyleKey = typeof(System.Windows.BidiControls.TextBox);
        }

        
        #endregion

        #region methods

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            TextElement = GetTemplateChild(TextElementName) as System.Windows.Controls.TextBox;
            
            if (TextElement != null)
            {
                TextElement.SelectionChanged += onInternalSelectionChanged;
                TextElement.TextChanged += onInternalTextChanged;
                TextElement.KeyDown += onInternalKeyDown;
                TextElement.KeyUp += onInternalKeyUp;

                TextElement.FontSource = FontSource;
                TextElement.HorizontalScrollBarVisibility = HorizontalScrollBarVisibility;
                TextElement.VerticalScrollBarVisibility = VerticalScrollBarVisibility;

                resetTextData(Text);
            }

            initDefaults();
        }

        void initDefaults()
        {

            //PATCH: workaround for DefaultValueAttribute bug
            if (!tapWasSet)
            {

                if (!HtmlPage.IsEnabled || String.IsNullOrEmpty(HtmlPage.Document.Body.GetAttribute("align")))
                {
                    //default
                    TextAlignment = base.FlowDirection == FlowDirection.LeftToRight ? TextAlignment.Left : TextAlignment.Right;
                }
                else
                {
                    string align = HtmlPage.Document.Body.GetAttribute("align");
                    TextAlignment = align.ToLower() == "right" ? TextAlignment.Right : TextAlignment.Left;
                }
            }
        }

        void onInternalKeyDown(object sender, KeyEventArgs e)
        {
            lastPos = TextElement.SelectionStart;
            lastStr = TextElement.Text;
            if (!IsShiftDown)
                IsShiftDown = e.Key == Key.Shift;
            if (!IsCtrlDown)
                IsCtrlDown = e.Key == Key.Ctrl;
            if (!IsAltDown)
                IsAltDown = e.Key == Key.Alt; //will not work sometimes
            
            e.Handled = true;
            switch (e.Key)
            { 
                case Key.Delete:
                    if (TextElement.SelectionLength > 0)
                    {
                        TextElement.Text = TextElement.Text.Remove(TextElement.SelectionStart, TextElement.SelectionLength);
                    }
                    else
                    {
                        if (FlowDirection == FlowDirection.RightToLeft)
                        {
                            if (TextElement.SelectionStart > 0)
                                TextElement.Text = TextElement.Text.Remove(TextElement.SelectionStart - 1, 1);
                        }
                        else
                        {
                            if (TextElement.SelectionStart < TextElement.Text.Length)
                                TextElement.Text = TextElement.Text.Remove(TextElement.SelectionStart, 1);
                        }
                    }
                    newPosOffset = 0;
                    break;
                case Key.Back:
                    if (TextElement.SelectionLength > 0)
                    {
                        TextElement.Text = TextElement.Text.Remove(TextElement.SelectionStart, TextElement.SelectionLength);
                        newPosOffset = 0;
                    }
                    else
                    {
                        if (FlowDirection == FlowDirection.RightToLeft)
                        {
                            if (TextElement.SelectionStart < TextElement.Text.Length)
                            {
                                TextElement.Text = TextElement.Text.Remove(TextElement.SelectionStart, 1);
                            }
                        }
                        else
                        {
                            if (TextElement.SelectionStart > 0)
                            {
                                TextElement.Text = TextElement.Text.Remove(TextElement.SelectionStart - 1, 1);
                            }
                        }
                    }
                    newPosOffset = 0;
                    break;
                case Key.C:
                    if (IsCtrlDown)
                        TextElement.GetClipboard(Text);
                    else
                        e.Handled = false;
                    break;
                case Key.V:
                    if (IsCtrlDown)
                    {
                        string tstr = TextElement.Text; //TODO:Either can test string.IsNullOrEmpty (Utils.ClipboardData), but should clean pseudo clipboard first
                        TextElement.SetClipboard();
                        e.Handled = TextElement.Text != tstr;
                    }
                    else
                        e.Handled = false;
                    break;
                case Key.Insert:
                    if (IsCtrlDown)
                        TextElement.GetClipboard(Text);
                    else if (IsShiftDown)
                    {
                        string tstr = TextElement.Text; //TODO:Either can test string.IsNullOrEmpty (Utils.ClipboardData), but should clean pseudo clipboard first                       
                        TextElement.SetClipboard();
                        e.Handled = TextElement.Text != tstr;
                    }
                    else
                        e.Handled = false;
                    break;
                //case Key.Home:
                //    if (IsShiftDown) { e.Handled = false; break; }
                //    if (FlowDirection == FlowDirection.RightToLeft)
                //        newPosOffset = TextElement.Text.Length - lastPos-1;
                //    else
                //        newPosOffset = -lastPos;
                //    break;                    
                //case Key.End:
                //    if (IsShiftDown) { e.Handled = false; break; }
                //    if (FlowDirection == FlowDirection.RightToLeft)
                //        newPosOffset = -lastPos;
                //    else
                //        newPosOffset = TextElement.Text.Length - lastPos;
                //    break;
                //case Key.Left:
                //    if (!IsShiftDown)
                //    {
                //        newPosOffset = -1;
                //        e.Handled = true;
                //    }
                //    break;
                //case Key.Right:
                //    if (!IsShiftDown)
                //    {
                //        newPosOffset = 1;
                //        e.Handled = true;
                //    }
                //    break;
                default: e.Handled = false; break;
            }
        }

        void onInternalKeyUp(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Ctrl: IsCtrlDown = false; break;
                case Key.Shift: IsShiftDown = false; break;
                case Key.Alt: IsAltDown = false; break;
                default:
                    
                    break;
            }
        }

        void onInternalTextChanged(object sender, TextChangedEventArgs e)
        {
            TextElement.TextChanged -= onInternalTextChanged;

            string resString = TextElement.Text;

            if (!string.IsNullOrEmpty(lastStr))
            {
                Differences<char> da = lastStr.Diff(resString);
                //TODO: VisualToLogicalPosition should be changed to VisualToLogical override with arrays for performance when it will be fixed!
                StringBuilder sb = new StringBuilder(Text);
                for (int i = 0; i < da.SourceDifferences.Count; i++)
                {
                    int rp = NBidi.NBidi.VisualToLogicalPosition(Text, da.SourceDifferences[i].Index) - 1;
                    rp = (rp >= sb.Length)? sb.Length-1:rp;
                    sb.Remove(rp < 0 ? 0 : rp, 1);
                }
                if (da.TargetDifferences.Count > 0)
                {
                    StringBuilder ns = new StringBuilder();
                    for (int i = 0; i < da.TargetDifferences.Count; i++)
                    {
                        ns.Append(da.TargetDifferences[i].Value);
                    }

                    if (da.TargetDifferences[0].Index < sb.Length)
                    {
                        int rp = NBidi.NBidi.VisualToLogicalPosition(lastStr, da.TargetDifferences[0].Index);
                        rp = rp < sb.Length ? rp : sb.Length;
                        sb.Insert(rp < 0 ? 0 : rp, ns.ToString());
                    }
                    else
                    {
                        sb.Append(ns.ToString());
                    }
                }

                resString = sb.ToString();
            }

            
            Text = resString;

            lastStr = string.Empty;
            int np = lastPos + newPosOffset;
            np = np > TextElement.Text.Length - 1 ? TextElement.Text.Length - 1 : np;
            np = np < 0 ? 0 : np;
            TextElement.SelectionStart = np;
            newPosOffset = 0;
            
            TextElement.TextChanged += onInternalTextChanged;
            if (TextChanged != null)
                TextChanged(sender, e);
        }

        void onInternalSelectionChanged(object sender, RoutedEventArgs e)
        {
            if (SelectionChanged != null)
                SelectionChanged(sender, e);
        }
        #endregion

        #region Template Parts
        internal System.Windows.Controls.TextBox TextElement { get; set; }
        internal const string TextElementName = "TextElement";
        #endregion

        #region events
        /// <summary>
        /// Occurs when content changes in the text box.
        /// </summary>
        public event TextChangedEventHandler TextChanged;

        /// <summary>
        /// Occurs when the text selection has changed.
        /// </summary>
        public event RoutedEventHandler SelectionChanged;
        #endregion
    }
}
